Mestr User Timing API'et for at skabe brugerdefinerede, meningsfulde performance-målinger. Gå ud over standard web vitals for at identificere flaskehalse og optimere brugeroplevelsen.
Mestring af Frontend Performance: Et Dybdegående Kig på User Timing API'et
I det moderne digitale landskab er frontend performance ikke en luksus; det er et fundamentalt krav for succes. For et globalt publikum kan en langsom, ikke-responsiv hjemmeside føre til brugerfrustration, nedsat engagement og en direkte negativ indvirkning på forretningsresultater. Vi har fremragende standardiserede målinger som Core Web Vitals (Largest Contentful Paint, First Input Delay, Cumulative Layout Shift), der giver os en grundlæggende forståelse af brugeroplevelsen. Men disse målinger, selvom de er afgørende, fortæller kun en del af historien.
Hvad med ydeevnen af applikationsspecifikke funktioner? Hvor lang tid tager det for søgeresultater at dukke op, efter en bruger har indtastet en forespørgsel? Hvor meget tid bruger din komplekse datavisualiseringskomponent på at rendere, efter den har modtaget data fra et API? Hvordan påvirker en ny funktion hastigheden af din single-page-applikations (SPA) ruteovergange? Standardmålinger kan ikke besvare disse detaljerede, forretningskritiske spørgsmål. Det er her, User Timing API'et kommer ind i billedet og giver udviklere mulighed for at skabe brugerdefinerede, højpræcisions performance-målinger, der er skræddersyet til deres unikke applikationer.
Denne omfattende guide vil føre dig igennem alt, hvad du behøver at vide for at udnytte User Timing API'et, fra de grundlæggende koncepter om mærker (marks) og målinger (measures) til avancerede teknikker ved hjælp af PerformanceObserver. Når du er færdig, vil du være rustet til at gå ud over generiske målinger og begynde at fortælle din applikations unikke performance-historie.
Hvad er Performance API'et? En Bredere Kontekst
Før vi dykker dybt ned i User Timing, er det vigtigt at forstå, at det er en del af en større pakke af værktøjer, der samlet er kendt som Performance API'et. Dette browser-API giver adgang til højpræcisions tidsdata relateret til navigation, ressourceindlæsning og mere. Det globale `window.performance`-objekt er din indgang til dette kraftfulde værktøjssæt.
Performance API'et består af flere grænseflader, herunder:
- Navigation Timing: Giver detaljerede tidsoplysninger om dokumentets navigationsproces, såsom den tid, der bruges på DNS-opslag, TCP-håndtryk og modtagelse af den første byte.
- Resource Timing: Tilbyder detaljerede netværks-tidsdata for hver ressource, der indlæses af siden, herunder billeder, scripts og CSS-filer.
- Paint Timing: Eksponerer tidsmålinger for First Paint og First Contentful Paint.
- User Timing: Fokus i vores artikel, som giver udviklere mulighed for at oprette deres egne brugerdefinerede tidsstempler (marks) og måle varigheden mellem dem (measures).
Disse API'er arbejder sammen for at give et holistisk billede af din applikations ydeevne. Vores mål i dag er at mestre User Timing-delen, som giver os magten til at tilføje vores egne brugerdefinerede kontrolpunkter til denne performance-tidslinje.
Kernekoncepterne: Mærker og Målinger
User Timing API'et er vildledende simpelt og kredser om to grundlæggende koncepter: mærker (marks) og målinger (measures). Tænk på det som at bruge et stopur. Du trykker på en knap for at markere en starttid, og du trykker på den igen for at markere en sluttid. Varigheden mellem disse to tryk er din måling.
Oprettelse af Performance-mærker: `performance.mark()`
Et 'mark' (mærke) er et navngivet tidsstempel med høj opløsning, der registreres på et bestemt tidspunkt i din applikations eksekvering. Det er som at plante et flag på din performance-tidslinje. Du kan oprette så mange mærker, som du har brug for, for at identificere nøglemomenter i en brugerrejse eller en komponents livscyklus.
Syntaksen er ligetil:
performance.mark(markName, [markOptions]);
markName: En streng, der repræsenterer det unikke navn for dit mærke. Vælg beskrivende navne!markOptions(valgfrit): Et objekt, der kan indeholde endetail-egenskab til at vedhæfte ekstra metadata, og enstartTimetil at specificere et brugerdefineret tidsstempel.
Grundlæggende Eksempel: Markering af en Begivenhed
Lad os sige, at vi vil markere begyndelsen af et vigtigt funktionskald.
function processLargeDataset() {
// Plant et flag lige før det tunge arbejde begynder
performance.mark('processLargeDataset:start');
// ... tung beregningslogik ...
console.log('Dataset processing complete.');
// Plant endnu et flag, når det er færdigt
performance.mark('processLargeDataset:end');
}
processLargeDataset();
I dette eksempel har vi oprettet to tidsstempler i browserens performance-tidslinje: `processLargeDataset:start` og `processLargeDataset:end`. Lige nu er de bare tidspunkter. Deres sande styrke frigøres, når vi bruger dem til at oprette en måling.
Tilføjelse af Kontekst med `detail`-egenskaben
Nogle gange er et tidsstempel alene ikke nok. Du vil måske inkludere ekstra kontekst om, hvad der skete i det øjeblik. `detail`-egenskaben er perfekt til dette. Den kan indeholde alle data, der kan strukturelt klones (som objekter, arrays, strenge, tal).
Forestil dig, at vi markerer starten på en komponent-rendering og vil vide, hvor mange elementer den renderede.
function renderProductList(products) {
const itemCount = products.length;
performance.mark('ProductList:render:start', {
detail: {
itemCount: itemCount,
source: 'initial-load'
}
});
// ... komponent-renderingslogik ...
performance.mark('ProductList:render:end');
}
const sampleProducts = new Array(1000).fill(0);
renderProductList(sampleProducts);
Denne yderligere kontekst er uvurderlig, når man senere analyserer performancedata. Du kunne for eksempel korrelere renderingstider med antallet af elementer for at se, om der er et lineært eller eksponentielt forhold.
Oprettelse af Performance-målinger: `performance.measure()`
En 'measure' (måling) fanger varigheden mellem to tidspunkter. Det er beregningen, der fortæller dig, "hvor lang tid" noget tog. Oftest vil du måle tiden mellem to af dine brugerdefinerede mærker.
Syntaksen har et par variationer:
performance.measure(measureName, startMarkOrOptions, [endMark]);
measureName: En streng, der repræsenterer det unikke navn for din måling.startMarkOrOptions(valgfrit): En streng med navnet på startmærket. Kan også være et options-objekt med `start`, `end`, `duration` og `detail`.endMark(valgfrit): En streng med navnet på slutmærket.
Grundlæggende Eksempel: Måling af en Funktions Varighed
Lad os bygge videre på vores `processLargeDataset`-eksempel og rent faktisk måle, hvor lang tid det tog.
function processLargeDataset() {
performance.mark('processLargeDataset:start');
// ... tung beregningslogik ...
performance.mark('processLargeDataset:end');
// Opret nu målingen
performance.measure(
'processLargeDataset:duration',
'processLargeDataset:start',
'processLargeDataset:end'
);
}
processLargeDataset();
Efter denne kode kører, vil browserens performance-buffer indeholde en ny post ved navn `processLargeDataset:duration`. Denne post vil have en `duration`-egenskab, der indeholder den præcise tid i millisekunder, der er forløbet mellem start- og slutmærkerne.
Avancerede Målingsscenarier
`measure()`-metoden er meget fleksibel. Du behøver ikke altid at angive to mærker.
- Fra Navigationsstart til et Mærke: Du kan måle tiden fra sidens navigation startede til et af dine brugerdefinerede mærker. Dette er utroligt nyttigt til at måle ting som "Time to Interactive Component".
// Mål fra navigationsstart, indtil hovedkomponenten er klar performance.measure('timeToInteractiveHeader', 'navigationStart', 'headerComponent:ready'); - Fra et Mærke til Nu: Hvis du udelader `endMark`, vil målingen blive beregnet fra dit `startMark` til det nuværende tidspunkt.
// Mål fra startmærket, indtil denne kodelinje eksekveres performance.measure('timeSinceDataRequest', 'api:fetch:start'); - Brug af Options-objektet: Du kan også sende et konfigurationsobjekt for at definere målingen, hvilket er nyttigt for at tilføje en `detail`-egenskab.
performance.measure('complexRender:duration', { start: 'complexRender:start', end: 'complexRender:end', detail: { renderType: 'canvas' } });
Adgang til og Rydning af Performance-poster
At oprette mærker og målinger er kun halvdelen af kampen. Du har brug for en måde at hente disse data på for at analysere dem. `performance`-objektet giver flere metoder til dette.
performance.getEntries(): Returnerer et array af alle performance-poster i bufferen (inklusive ressourcetider, navigationstider osv.).performance.getEntriesByType(type): Returnerer et array af poster af en bestemt type. Du vil oftest bruge `performance.getEntriesByType('mark')` og `performance.getEntriesByType('measure')`.performance.getEntriesByName(name, [type]): Returnerer et array af poster med et specifikt navn (og valgfrit, en specifik type).
Eksempel: Logning af Målinger til Konsollen
// Efter at have kørt vores tidligere eksempler...
const allMeasures = performance.getEntriesByType('measure');
console.log(allMeasures);
// Et måle-post-objekt ser nogenlunde sådan her ud:
// {
// "name": "processLargeDataset:duration",
// "entryType": "measure",
// "startTime": 12345.67,
// "duration": 150.89
// }
const specificMeasure = performance.getEntriesByName('processLargeDataset:duration');
console.log(`Processing took: ${specificMeasure[0].duration}ms`);
Vigtigt: Oprydning i Performance-bufferen
Browserens performance-buffer er ikke uendelig. For at forhindre hukommelseslækager og holde dine målinger relevante, er det god praksis at rydde de mærker og målinger, du har oprettet, når du er færdig med dem.
performance.clearMarks([name]): Rydder alle mærker, eller kun mærker med det angivne navn.performance.clearMeasures([name]): Rydder alle målinger, eller kun målinger med det angivne navn.
Et almindeligt mønster er at hente dataene, behandle eller sende dem, og derefter rydde dem.
function analyzeAndClear() {
const myMeasures = performance.getEntriesByName('processLargeDataset:duration');
// Send myMeasures til en analytics-tjeneste...
sendToAnalytics(myMeasures);
// Ryd op for at frigøre hukommelse
performance.clearMarks('processLargeDataset:start');
performance.clearMarks('processLargeDataset:end');
performance.clearMeasures('processLargeDataset:duration');
}
Praktiske Anvendelsesmuligheder for User Timing i den Virkelige Verden
Nu hvor vi forstår mekanikken, lad os undersøge, hvordan man anvender User Timing API'et til at løse performance-udfordringer i den virkelige verden. Disse eksempler er framework-agnostiske og kan tilpasses enhver frontend-stack.
1. Måling af Varighed på API-kald
At forstå, hvor længe din applikation venter på data, er afgørende. Du kan nemt omkranse din dataindhentningslogik med mærker og målinger.
async function fetchUserData(userId) {
const markStart = `api:getUser:${userId}:start`;
const markEnd = `api:getUser:${userId}:end`;
const measureName = `api:getUser:${userId}:duration`;
performance.mark(markStart);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
// Du kan endda tilføje detaljer om fejl!
performance.mark(markEnd, { detail: { status: 'error', message: error.message } });
} finally {
// Sikr, at slutmærket og målingen altid oprettes
if (performance.getEntriesByName(markEnd).length === 0) {
performance.mark(markEnd, { detail: { status: 'success' } });
}
performance.measure(measureName, markStart, markEnd);
}
}
fetchUserData('123');
Dette mønster giver præcise tidsmålinger for hvert API-kald, hvilket giver dig mulighed for at identificere langsomme endpoints direkte fra rigtige brugerdata.
2. Sporing af Komponent-renderingstider i SPA'er
For frameworks som React, Vue eller Angular er måling af den tid, det tager for en komponent at mounte og rendere, en primær anvendelse. Dette hjælper med at identificere komplekse komponenter, der kan bremse din applikation.
Eksempel med React Hooks:
import React, { useLayoutEffect, useEffect, useRef } from 'react';
function MyHeavyComponent({ data }) {
const componentId = useRef(`MyHeavyComponent-${Math.random()}`).current;
const markStartName = `${componentId}:render:start`;
const markEndName = `${componentId}:render:end`;
const measureName = `${componentId}:render:duration`;
// useLayoutEffect kører synkront efter alle DOM-mutationer.
// Det er det perfekte sted at markere starten på renderingsmålingen.
useLayoutEffect(() => {
performance.mark(markStartName);
}, []); // Kør kun ved første mount
// useEffect kører asynkront efter render er committet til skærmen.
// Dette er et godt sted at markere slutningen.
useEffect(() => {
performance.mark(markEndName);
performance.measure(measureName, markStartName, markEndName);
// Log resultatet for demonstration
const measure = performance.getEntriesByName(measureName)[0];
if (measure) {
console.log(`${measureName} took ${measure.duration}ms`);
}
// Oprydning
performance.clearMarks(markStartName);
performance.clearMarks(markEndName);
performance.clearMeasures(measureName);
}, []); // Kør kun ved første mount
return (
// ... JSX for den tunge komponent ...
);
}
3. Kvantificering af Kritiske Brugerrejser
Den mest effektfulde brug af User Timing er at måle brugerinteraktioner i flere trin, der er kritiske for din forretning. Dette overskrider simple tekniske målinger og måler den opfattede hastighed af din applikations kernefunktionalitet.
Overvej en checkout-proces i en e-handelsbutik:
const checkoutButton = document.getElementById('checkout-btn');
checkoutButton.addEventListener('click', () => {
// 1. Brugeren klikker på 'checkout'-knappen
performance.mark('checkout:journey:start');
// ... kode til at validere kurv, navigere til betalingsside, osv. ...
});
// På betalingssiden, efter betalingsformularen er renderet og interaktiv
function onPaymentFormReady() {
performance.mark('checkout:paymentForm:ready');
performance.measure('checkout:timeToPaymentForm', 'checkout:journey:start', 'checkout:paymentForm:ready');
}
// Efter betalingen er succesfuldt behandlet og bekræftelsesskærmen vises
function onPaymentSuccess() {
performance.mark('checkout:journey:end');
performance.measure('checkout:totalJourney:duration', 'checkout:journey:start', 'checkout:journey:end');
// Nu har du to stærke målinger at analysere og optimere.
}
4. A/B-test af Performance-forbedringer
Når du refaktorerer et stykke kode eller introducerer en ny algoritme, hvordan beviser du så, at det rent faktisk er hurtigere for rigtige brugere? User Timing giver objektive data til A/B-test.
Forestil dig, at du har to forskellige sorteringsalgoritmer, du vil teste:
function sortProducts(products, algorithmVersion) {
const markStart = `sort:v${algorithmVersion}:start`;
const markEnd = `sort:v${algorithmVersion}:end`;
const measureName = `sort:v${algorithmVersion}:duration`;
performance.mark(markStart);
if (algorithmVersion === 'A') {
// ... kør gammel sorteringsalgoritme ...
} else {
// ... kør ny, optimeret sorteringsalgoritme ...
}
performance.mark(markEnd);
performance.measure(measureName, markStart, markEnd);
}
// Baseret på et A/B-test-flag ville du kalde den ene eller den anden.
// Senere, i din analytics, kan du sammenligne den gennemsnitlige varighed af
// 'sort:vA:duration' vs 'sort:vB:duration' for at se, hvilken der var hurtigst.
Visualisering og Analyse af Dine Brugerdefinerede Målinger
At oprette brugerdefinerede målinger er nytteløst, hvis du ikke analyserer dataene. Der er to primære måder at gribe dette an på: lokalt under udvikling og aggregeret i produktion.
Brug af Browser Developer Tools
Moderne browsere som Chrome og Firefox har fremragende understøttelse for visualisering af User Timing-mærker og -målinger i deres performance-profileringsværktøjer.
- Åbn din browsers Developer Tools (F12 eller Ctrl+Shift+I).
- Gå til Performance-fanen.
- Start en profiloptagelse og udfør derefter de handlinger i din app, der udløser dine brugerdefinerede mærker og målinger.
- Stop optagelsen.
I tidslinjevisningen vil du finde en dedikeret række kaldet Timings. Dine brugerdefinerede mærker vil fremstå som lodrette linjer, og dine målinger vil blive vist som farvede bjælker, der viser deres varighed. Ved at holde musen over dem afsløres deres navne og præcise tider. Dette er en utrolig kraftfuld måde at fejlfinde performance-problemer på under udvikling.
Afsendelse af Data til Analytics- og RUM-tjenester
Til produktionsmonitorering skal du indsamle disse data fra dine brugere og sende dem til en central placering for aggregering og analyse. Dette er en kernedel af Real User Monitoring (RUM).
Den generelle arbejdsgang er:
- Indsaml de performance-målinger, du er interesseret i.
- Formater dem til en passende payload (f.eks. JSON).
- Send payloaden til et analytics-endpoint. Dette kan være en tredjepartstjeneste som Datadog, New Relic, Sentry eller endda Google Analytics (via brugerdefinerede events), eller en brugerdefineret backend, du selv kontrollerer.
function sendPerformanceData() {
// Vi er kun interesserede i vores brugerdefinerede applikationsmålinger
const appMeasures = performance.getEntriesByType('measure').filter(
(entry) => entry.name.startsWith('app:') // Brug en navnekonvention!
);
if (appMeasures.length > 0) {
const payload = JSON.stringify(appMeasures.map(measure => ({
name: measure.name,
duration: measure.duration,
startTime: measure.startTime,
details: measure.detail, // Send vores rige kontekst
path: window.location.pathname // Tilføj mere kontekst
})));
// Brug navigator.sendBeacon for pålidelig, ikke-blokerende dataafsendelse
navigator.sendBeacon('https://analytics.example.com/performance', payload);
// Ryd op i de målinger, der er blevet sendt
appMeasures.forEach(measure => {
performance.clearMeasures(measure.name);
// Ryd også de tilknyttede mærker
});
}
}
// Kald denne funktion på et passende tidspunkt, f.eks. når siden er ved at blive lukket
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendPerformanceData();
}
});
Avancerede Teknikker og Bedste Praksis
For virkelig at mestre User Timing API'et, lad os se på nogle avancerede funktioner og bedste praksis, der vil gøre din instrumentering mere robust og effektiv.
Brug af `PerformanceObserver` til Asynkron Overvågning
`getEntries*()`-metoderne kræver, at du manuelt poller performance-bufferen. Dette har to ulemper: du kan køre din kontrol for sent og gå glip af poster, hvis bufferen er blevet fyldt op og ryddet, og selve pollingen kan have en mindre performance-omkostning. Den moderne, foretrukne løsning er `PerformanceObserver`.
En `PerformanceObserver` giver dig mulighed for at abonnere på performance-post-hændelser. Din callback-funktion vil blive kaldt asynkront, når nye poster af de typer, du observerer, registreres.
// 1. Opret en callback-funktion til at håndtere nye poster
const observerCallback = (list) => {
for (const entry of list.getEntries()) {
console.log('New measure observed:', entry.name, entry.duration);
// Her kan du øjeblikkeligt sende posten til din analytics-tjeneste
// uden at skulle polle eller vente.
}
};
// 2. Opret observer-instansen
const observer = new PerformanceObserver(observerCallback);
// 3. Start med at observere for 'mark'- og 'measure'-posttyper
// 'buffered: true'-optionen sikrer, at du får poster, der blev oprettet
// *før* observeren blev registreret.
observer.observe({ entryTypes: ['mark', 'measure'], buffered: true });
// Nu, hver gang performance.mark() eller performance.measure() kaldes et sted
// i din applikation, vil observerCallback blive udløst med den nye post.
// For at stoppe med at observere senere:
// observer.disconnect();
Brug af `PerformanceObserver` er mere effektivt, mere pålideligt og bør være dit standardvalg til indsamling af performancedata i et produktionsmiljø.
Etablér en Klar Navnekonvention
Efterhånden som din applikation vokser, vil du akkumulere mange brugerdefinerede målinger. Uden en konsekvent navnekonvention vil dine data blive svære at filtrere og analysere. Anvend et mønster, der giver kontekst.
En god konvention kunne være: [appName]:[featureOrComponent]:[eventName]:[status]
ecom:ProductGallery:render:startecom:ProductGallery:render:endecom:ProductGallery:render:durationadmin:DataTable:fetchApi:startadmin:DataTable:fetchApi:duration
Denne struktur gør det trivielt at filtrere efter alle målinger relateret til `ProductGallery` eller at finde alle `fetchApi`-varigheder på tværs af hele applikationen.
Abstraher til en Hjælpetjeneste (Utility Service)
For at sikre konsistens og reducere boilerplate-kode, bør du pakke `performance`-kaldene ind i dit eget hjælpe-modul eller -tjeneste. Dette gør det også nemt at aktivere eller deaktivere performance-overvågning baseret på miljøet.
// performance-service.js
const IS_PERFORMANCE_MONITORING_ENABLED = process.env.NODE_ENV === 'production' || window.location.search.includes('perf=true');
export const perfMark = (name, options) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.mark(name, options);
};
export const perfMeasure = (name, start, end) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.measure(name, start, end);
};
export const startJourney = (name) => {
perfMark(`${name}:start`);
};
export const endJourney = (name) => {
const startMark = `${name}:start`;
const endMark = `${name}:end`;
const measureName = `${name}:duration`;
perfMark(endMark);
perfMeasure(measureName, startMark, endMark);
// Optionally clear the marks here
};
// I din komponent:
// import { startJourney, endJourney } from './performance-service';
// startJourney('ecom:checkout');
// ...senere...
// endJourney('ecom:checkout');
Konklusion: Tag Kontrol over Din Applikations Performance-historie
Mens standardmålinger som Core Web Vitals giver et essentielt sundhedstjek for din hjemmeside, belyser de ikke ydeevnen af de funktioner og interaktioner, der gør din applikation unik. User Timing API'et er broen, der lukker dette hul. Det giver en simpel, men dybt kraftfuld mekanisme til at måle, hvad der virkelig betyder noget for dine brugere og din forretning.
Ved at implementere brugerdefinerede mærker og målinger omdanner du performance-optimering fra et gættespil til en datadrevet videnskab. Du kan præcist identificere de funktioner, komponenter eller brugerflows, der forårsager flaskehalse, validere effekten af dine refaktoreringstiltag med objektive tal, og i sidste ende bygge en hurtigere og mere behagelig oplevelse for dit globale publikum.
Start i det små. Identificer den allermest kritiske brugerrejse i din applikation—om det er at søge efter et produkt, indsende en formular eller indlæse et data-dashboard. Instrumenter den med `performance.mark()` og `performance.measure()`. Analyser resultaterne i dine udviklerværktøjer. Når du først ser den klarhed, det giver, vil du være bemyndiget til at fortælle din applikations komplette performance-historie, én brugerdefineret måling ad gangen.